#include "PrecompiledHeader_Reborn.h"
#include "CelestialPlanetQuad.h"

/*--------------------------------------------------------------------------*/
uint CelestialPlanetQuad::quadID = 1;
/*--------------------------------------------------------------------------*/

CelestialPlanetQuad::CelestialPlanetQuad(const NodeCreation& nc, const QuadCreation& qc) :
	IRenderableObject(nc)
{
	this->AddFlag(ENodeFlag::NoActive);

	this->m_creation = qc;
	this->m_vao = NULL;
	this->m_childs[0] = this->m_childs[1] = this->m_childs[2] = this->m_childs[3] = NULL;
	this->m_childrenPhase = EQuadPreparations::_1_PrepareMyData;

	RenderTaskDesc desc;
	desc.m_material = g_Material->GetMaterial("mat_terrain");
	desc.m_argument = 10;
	desc.m_flags = ERenderTaskFlag::SortByDist;
	desc.m_priority = 0;
	desc.m_object = this;
	this->AddRenderTask(desc);

	desc.m_argument = 12;
	desc.m_flags = ERenderTaskFlag::UserInterface | ERenderTaskFlag::NoMaterial;
	this->AddRenderTask(desc);

	desc.m_argument = 999;
	desc.m_flags = ERenderTaskFlag::NoMaterial;
	desc.m_priority = 0;
	desc.m_object = this;
	desc.m_material = NULL;
	this->AddRenderTask(desc);

	this->SetOcclusionTestEnabled(false);

	float position = qc.m_planet->GetPlanetRadius();

	if (this->m_creation.m_quadPosition == EQuadPosition::Ignore)
	{
		this->SetPosition(this->m_creation.m_planet->TranslationVector(this->m_creation.m_quadLocation));
	}

}

/*--------------------------------------------------------------------------*/

CelestialPlanetQuad::~CelestialPlanetQuad()
{

}

/*--------------------------------------------------------------------------*/

void CelestialPlanetQuad::RenderableBinder(uint treeCalcFlags)
{
	float distance = glm::distance(glm::vec3(this->GetModelMatrix()[3][0],
	                                         this->GetModelMatrix()[3][1],
	                                         this->GetModelMatrix()[3][2]) - this->GetSceneInstance()->GetCamera()->GetPosition(),
	                               glm::vec3(0.0f));

	if (distance < this->GetRadius() * 1.1f && this->GetRadius() > 100.0f)
	{
		if (this->m_childrenPhase == EQuadPreparations::_3_WaitForChildrenOrder)
		{
			this->m_childrenPhase = EQuadPreparations::_4_PrepareChildrenData;
		}

		if (this->m_childrenPhase == EQuadPreparations::_4_PrepareChildrenData)
		{
			this->BindRenderTask(this->GetSceneInstance()->GetRenderPhase(), 999);;
		}

		if (this->m_childrenPhase == EQuadPreparations::_5_AllPrepared)
		{
			this->DeleteFlag(ENodeFlag::IgnoreChildren);
		}

		bool useMe = false;

		for (uint i = 0; i < 4; ++i)
		{
			if (this->m_childs[i] && this->m_childs[i]->m_childrenPhase < EQuadPreparations::_2_TransferMyData)
			{
				useMe = true;
				break;
			}
		}

		if (useMe)
		{
			this->BindRenderTask(this->GetSceneInstance()->GetRenderPhase(), 10);
		}
	}
	else
	{
		this->AddFlag(ENodeFlag::IgnoreChildren);
		this->BindRenderTask(this->GetSceneInstance()->GetRenderPhase(), 10);
	}
}

/*--------------------------------------------------------------------------*/

void CelestialPlanetQuad::RenderableHelper(const uint i, ERenderQuality::Type quality, uint pendingInstances)
{
	switch (i)
	{
	case 10:
	{
		if (this->m_vao)
		{
#ifdef VL_RENDER_STATISTICS
			g_Hud->GetStatisticsManager()->IncrementStatistic("CelestialPlanetQuad");
#endif
			ROpenGL::BindVertexArray(this->m_vao);

			if (g_Keyboard->GetKeyState(EKey::k_lalt))
			{
				glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
				ROpenGL::DrawVertexArray(0);
				glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			}
			else
			{
				//std::cout << this->m_creation.m_quadLocation << " " << this->GetScale() << " " << this->GetPosition() << this->GetModelMatrix() << std::endl;
				ROpenGL::DrawVertexArray(0);
			}
		}

		break;
	}

	case 12:
	{
		float r = this->GetRadius();

		g_Renderer->DrawLine3D(glm::vec3(-r, 0.0f, 0.0f), glm::vec3(r, 0.0f, 0.0f), NULL, Color::Green);
		g_Renderer->DrawLine3D(glm::vec3(0.0f, -r, 0.0f), glm::vec3(0.0f, r, 0.0f), NULL, Color::Green);
		g_Renderer->DrawLine3D(glm::vec3(0.0f, 0.0f, -r), glm::vec3(0.0f, 0.0f, r), NULL, Color::Green);
		break;
	}

	case 999:
	{
		if (this->m_childrenPhase == EQuadPreparations::_4_PrepareChildrenData)
		{
			this->PrepareChildren();
		}

		return;
	}
	}
}

/*--------------------------------------------------------------------------*/

void CelestialPlanetQuad::OnPreCalc(uint arg)
{
	switch (this->m_childrenPhase)
	{
	case EQuadPreparations::_1_PrepareMyData:
	{
		this->m_childrenPhase = EQuadPreparations::_11_WaitForAsync;
		this->SetCalcFlag(arg, ECalc::SingleThreadLowPriority);
		this->StartAsync(0);
		break;
	}

	case EQuadPreparations::_11_WaitForAsync:
	{
		break;
	}

	case EQuadPreparations::_2_TransferMyData:
	{
		this->TransferToGPU();
		break;
	}

	case EQuadPreparations::_3_WaitForChildrenOrder:
	{
		break;
	}

	case EQuadPreparations::_4_PrepareChildrenData:
	{
		break;
	}

	case EQuadPreparations::_5_AllPrepared:
	{
		this->SetCalcFlag(arg, ECalc::MultiThreadHighPriority);
		break;
	}

	default:
	{
		AssertLogC(true, "Unknown EQuadPreparation");
	}
	}
}

/*--------------------------------------------------------------------------*/

#define QUAD_X		128
#define QUAD_Y		128
#define NUMINDICES	(QUAD_X * QUAD_Y + (QUAD_X - 1) * (QUAD_Y - 2))
#define NUMPOINTS	(QUAD_X * QUAD_Y)

void CelestialPlanetQuad::CalculateQuad()
{
#ifdef DEBUG_QUADS
	std::cout << "CalculateQuad: " << this << std::endl;
#endif

	this->m_data.m_positions = new glm::vec3[NUMPOINTS];
	this->m_data.m_normals = new glm::vec3[NUMPOINTS];
	this->m_data.m_texcoords = new glm::vec2[NUMPOINTS];
	this->m_data.m_indices = new uint[NUMINDICES];

	int counter = 0;
	glm::vec3 offset = this->m_creation.m_planet->RotateN(this->GetCubeLocation(), this->m_creation.m_quadLocation) * this->m_creation.m_planet->GetPlanetRadius();
	float radius = 0.0f;

	for (uint x = 0; x < QUAD_X; ++x)
	{
		float invQuadX = ((float) x / (QUAD_X - 1) * 2.0f) - 1.0f;

		for (uint y = 0; y < QUAD_Y; ++y)
		{
			float invQuadY = ((float) y / (QUAD_Y - 1) * 2.0f) - 1.0f;
			glm::vec2 pos2d = this->m_creation.m_location + (0.5f * this->m_creation.m_size * glm::vec2(invQuadX, invQuadY));
			glm::vec3 nvec = this->m_creation.m_planet->RotateN(pos2d, this->m_creation.m_quadLocation);

			this->m_data.m_positions[counter] = this->m_creation.m_planet->Position(nvec);
			this->m_data.m_normals[counter] = this->m_creation.m_planet->Normal(pos2d, this->m_creation.m_quadLocation);
			this->m_data.m_texcoords[counter] = glm::vec2((float) x / (QUAD_X - 1), (float) y / (QUAD_Y - 1));

			this->m_data.m_positions[counter] -= offset;

			radius = glm::max(radius, glm::distance(glm::vec3(), this->m_data.m_positions[counter]));

			++counter;
		}
	}

	/*--------------------------------------------------------------------------*/

	float invRadius = 1.0f / radius;
	counter = 0;

	for (uint x = 0; x < QUAD_X; ++x)
	{
		for (uint y = 0; y < QUAD_Y; ++y)
		{
			this->m_data.m_positions[counter] *= invRadius;
			++counter;
		}
	}

	/*--------------------------------------------------------------------------*/

	int c = 0;

	for (int row = 0; row < QUAD_Y - 1; ++row)
	{
		if ((row % 2) == 0)   // even rows
		{
			for (int col = 0; col < QUAD_X; ++col)
			{
				this->m_data.m_indices[c++] = col + row * QUAD_X;
				this->m_data.m_indices[c++] = col + (row + 1) * QUAD_X;
			}
		}
		else   // odd row
		{
			for (int col = QUAD_X - 1; col > 0; --col)
			{
				this->m_data.m_indices[c++] = col + (row + 1) * QUAD_X;
				this->m_data.m_indices[c++] = col - 1 + row * QUAD_Y;
			}
		}
	}

	if ((QUAD_Y & 1) && QUAD_Y > 2)
	{
		this->m_data.m_indices[c] = (QUAD_Y - 1) * QUAD_X;
	}

	if (this->m_creation.m_quadPosition == EQuadPosition::Ignore)
	{
		this->SetRadius(this->m_creation.m_planet->GetPlanetRadius() * 2.0f);
	}
	else
	{
		this->SetRadius(radius);
	}

	this->SetScale(radius);
}

/*--------------------------------------------------------------------------*/

static void TransferDataToGPU(GLVertexBuffer* vb, uint size, void* data)
{
	glm::vec3* mapped = (glm::vec3*) ROpenGL::MapBuffer(vb);
	memcpy(mapped, data, size);
	ROpenGL::UnmapBuffer(vb);
}

void CelestialPlanetQuad::TransferToGPU()
{
#ifdef DEBUG_QUADS
	std::cout << "TransferToGPU: " << this << std::endl;
#endif
	AssertLogC(this->m_vao != NULL, "VAO already created!");

	std::stringstream ss;
	ss << "CEL_PLA_QUA_" << CelestialPlanetQuad::quadID++ << "_";
	std::string name = ss.str();

	GLVertexBuffer* vb_pos = ROpenGL::GenBuffer(name + "POS", EVertexDeclaration::Position0, NUMPOINTS);
	TransferDataToGPU(vb_pos, NUMPOINTS * sizeof(glm::vec3), (void*) this->m_data.m_positions);

	GLVertexBuffer* vb_nrm = ROpenGL::GenBuffer(name + "NRM", EVertexDeclaration::Normal0, NUMPOINTS);
	TransferDataToGPU(vb_nrm, NUMPOINTS * sizeof(glm::vec3), (void*) this->m_data.m_normals);

	GLVertexBuffer* vb_tex = ROpenGL::GenBuffer(name + "TEX", EVertexDeclaration::Texcoord0, NUMPOINTS);
	TransferDataToGPU(vb_tex, NUMPOINTS * sizeof(glm::vec2), (void*) this->m_data.m_texcoords);

	GLVertexBuffer* vb_idb = ROpenGL::GenBuffer(name + "IDB", EVertexDeclaration::Indices, NUMINDICES);
	TransferDataToGPU(vb_idb, NUMINDICES * sizeof(uint), (void*) this->m_data.m_indices);

	this->m_vao = ROpenGL::GenVertexArray(name + "VAO", GL_TRIANGLE_STRIP, NUMINDICES);
	ROpenGL::BindVertexArray(this->m_vao);

	ROpenGL::AttachBuffer(vb_pos);
	ROpenGL::AttachBuffer(vb_nrm);
	ROpenGL::AttachBuffer(vb_tex);
	ROpenGL::AttachBuffer(vb_idb);

	ROpenGL::UnbindVertexArray();
	ROpenGL::UnbindBuffers();

	ROpenGL::UniqueBufferOwner(this->m_vao, vb_pos);
	ROpenGL::UniqueBufferOwner(this->m_vao, vb_nrm);
	ROpenGL::UniqueBufferOwner(this->m_vao, vb_tex);
	ROpenGL::UniqueBufferOwner(this->m_vao, vb_idb);

	delete [] this->m_data.m_positions;
	this->m_data.m_positions = NULL;

	delete [] this->m_data.m_normals;
	this->m_data.m_normals = NULL;

	delete [] this->m_data.m_texcoords;
	this->m_data.m_texcoords = NULL;

	delete [] this->m_data.m_indices;
	this->m_data.m_indices = NULL;

	this->m_childrenPhase = EQuadPreparations::_3_WaitForChildrenOrder;
}

/*--------------------------------------------------------------------------*/

void CelestialPlanetQuad::PrepareChildren()
{
#ifdef DEBUG_QUADS
	std::cout << "PrepareChildren: " << this << std::endl;
#endif

	CelestialPlanet* cp = this->m_creation.m_planet;
	EQuadLocation::Enum ql = this->m_creation.m_quadLocation;

	QuadCreation qc;
	qc.m_size = 0.5f * this->m_creation.m_size;
	qc.m_planet = this->m_creation.m_planet;
	qc.m_quadLocation = this->m_creation.m_quadLocation;
	qc.m_depth = this->m_creation.m_depth + 1;

	NodeCreation nc;
	nc.m_flags = 0;
	nc.m_parent = this;
	nc.m_scene = this->GetSceneInstance();

	glm::vec3 translation = cp->TranslationVector(ql);

	float qhalf = this->m_creation.m_size.x * 0.25f;
	AssertLogC((this->m_childs[0] || this->m_childs[1] || this->m_childs[2] || this->m_childs[3]) != 0, "Overwriting quads?!");

	/*--------------------------------------------------------------------------*/

	qc.m_quadPosition = EQuadPosition::NN;
	qc.m_location = this->m_creation.m_location + glm::vec2(-qhalf, -qhalf);
	this->m_childs[0] = new CelestialPlanetQuad(nc, qc);
	this->m_childs[0]->SetPosition((cp->RotateN(qc.m_location, ql) * cp->GetPlanetRadius()) - (cp->RotateN(this->GetCubeLocation(), ql) * cp->GetPlanetRadius()));
	this->m_childs[0]->SetRadius(this->GetRadius() * 0.5f);
	/*--------------------------------------------------------------------------*/

	qc.m_quadPosition = EQuadPosition::NP;
	qc.m_location = this->m_creation.m_location + glm::vec2(-qhalf, qhalf);
	this->m_childs[1] = new CelestialPlanetQuad(nc, qc);
	this->m_childs[1]->SetPosition((cp->RotateN(qc.m_location, ql) * cp->GetPlanetRadius()) - (cp->RotateN(this->GetCubeLocation(), ql) * cp->GetPlanetRadius()));
	this->m_childs[1]->SetRadius(this->GetRadius() * 0.5f);
	/*--------------------------------------------------------------------------*/

	qc.m_quadPosition = EQuadPosition::PP;
	qc.m_location = this->m_creation.m_location + glm::vec2(qhalf, qhalf);
	this->m_childs[2] = new CelestialPlanetQuad(nc, qc);
	this->m_childs[2]->SetPosition((cp->RotateN(qc.m_location, ql) * cp->GetPlanetRadius()) - (cp->RotateN(this->GetCubeLocation(), ql) * cp->GetPlanetRadius()));
	this->m_childs[2]->SetRadius(this->GetRadius() * 0.5f);
	/*--------------------------------------------------------------------------*/

	qc.m_quadPosition = EQuadPosition::PN;
	qc.m_location = this->m_creation.m_location + glm::vec2(qhalf, -qhalf);
	this->m_childs[3] = new CelestialPlanetQuad(nc, qc);
	this->m_childs[3]->SetPosition((cp->RotateN(qc.m_location, ql) * cp->GetPlanetRadius()) - (cp->RotateN(this->GetCubeLocation(), ql) * cp->GetPlanetRadius()));
	this->m_childs[3]->SetRadius(this->GetRadius() * 0.5f);
	/*--------------------------------------------------------------------------*/

	this->m_childrenPhase = EQuadPreparations::_5_AllPrepared;
}

/*--------------------------------------------------------------------------*/

glm::vec2 CelestialPlanetQuad::GetCubeLocation()
{
	return this->m_creation.m_location;
}

/*--------------------------------------------------------------------------*/

EQuadPreparations::Phase CelestialPlanetQuad::GetPhase()
{
	return this->m_childrenPhase;
}

/*--------------------------------------------------------------------------*/

uint CelestialPlanetQuad::OnAsyccCalc(uint arg)
{
	switch (arg)
	{
	case 0:
	{
		this->CalculateQuad();
		break;
	}
	}

	return arg;
}

/*--------------------------------------------------------------------------*/

void CelestialPlanetQuad::OnAsyncReady(uint arg)
{
	switch (arg)
	{
	case 0:
	{
		this->m_childrenPhase = EQuadPreparations::_2_TransferMyData;
		break;
	}
	}
}

/*--------------------------------------------------------------------------*/